from typing import List, Type, Dict, Optional

from emi.bar.storage.adapter import bario_adapter_manager
from emi.bar.storage.model import BaseDBModel
from emi.bar.db import BarDB, DayDB
from emi.core.Context import Context
from emi.core.DBSource import DBSource
from emi.util import  TimeUtils
from emi.bar.data import BarCustom, Interval, BarData
from emi.util import SqliteUtil
import peewee
from datetime import datetime

def to_datetime(yy_mm_dd:int,the_day_end = False)->datetime:
    year = int(yy_mm_dd / 10000 + 0.01)
    month = int((yy_mm_dd / 100 + 0.01)) % 100
    day = yy_mm_dd % 100
    if the_day_end:
        return datetime(year=year, month=month, day=day, hour=23, minute=59, second=59)
    else:
        return datetime(year=year, month=month, day=day, hour=0, minute=0, second=0)


class BarDataModel(BaseDBModel):

    symbol: int = peewee.CharField(index=True,max_length=64)
    year_month_day:int = peewee.SmallIntegerField(index=True)
    interval: int = peewee.SmallIntegerField(index=True)
    id_version: int = peewee.SmallIntegerField()  ##版本号和SeraId
    core_data: bytes = peewee.BlobField(null=True)
    detail_data:bytes = peewee.BlobField(null=True)


    def to_dict(self):
        return self.__data__

    @staticmethod
    def from_data(bar: BarData):


        adapter = bario_adapter_manager.get_adapter_by_bar_type(bar.__class__)

        version = adapter.CUR_VERSION
        ## version和serial_id是压缩的。
        assert version < 100
        db_bar = BarDataModel()
        db_bar.symbol = bar.symbol
        db_bar.interval = bar.interval.value
        y_m_d, h_m_s = TimeUtils.split_datetime(bar.datetime)
        db_bar.year_month_day = y_m_d
        db_bar.id_version = adapter.serial_id * 100 + version
        db_bar.core_data = adapter.to_core_bytes(bar)
        db_bar.detail_data = adapter.to_detail_bytes(bar)

        return db_bar


    def to_bar(self)->BarData:
        seial_id = int(self.id_version / 100)
        version = self.id_version % 100
        adapter = bario_adapter_manager.get_adapter_by_serial_id(seial_id)

        class_type = adapter.class_type
        interval = Interval(self.interval)
        year = int(self.year_month_day / 10000 + 0.01)
        month = int((self.year_month_day / 100 + 0.01)) % 100
        day = self.year_month_day % 100
        time = datetime(year=year, month=month, day=day, hour=15, minute=0, second=0)
        bar = class_type(interval = interval,datetime = time,symbol = self.symbol)
        adapter.load_core_bytes(bar,self.core_data,version)
        adapter.load_detail_bytes(bar,self.detail_data,version)
        return bar

class BarDBStorage(BarDB):

    def __init__(self,source:DBSource,indexes_desc = None,table_name = None):
        if indexes_desc is None:
            indexes_desc = (
                    (("symbol", "year_month_day","interval"), True),
                )
        if table_name is None:
            table_name = 'bar_data'
        self.source = source.init_peewee(table_name,BarDataModel,indexes=indexes_desc,ignore_inited=True)

    def get_bars(self, symbol: str, interval: Interval = None, start: datetime = None,
                 end: datetime = None, with_detail=True) -> List[BarData]:
        if start is None:
            start = datetime(year=1970, month=1, day=1, hour=0, minute=0, second=0)
        if end is None:
            end = datetime.now()
        if interval is None:
            interval = self.get_default_interval()

        start_y_m_d, h_m_s = TimeUtils.split_datetime(start)
        end_y_m_d, h_m_s = TimeUtils.split_datetime(end)
        interval = interval.value
        with self.source.use_peewee() as db_model:
            if with_detail:
                fields = (db_model.symbol,db_model.year_month_day,db_model.interval,db_model.id_version,db_model.core_data,db_model.detail_data)
            else:
                fields = (db_model.symbol,db_model.year_month_day,db_model.interval,db_model.id_version,db_model.core_data)

            if symbol is None:
                s = (
                    db_model.select(*fields)
                        .where(
                        (db_model.interval == interval)
                        & (db_model.year_month_day >= start_y_m_d)
                        & (db_model.year_month_day <= end_y_m_d)
                    ).order_by(db_model.year_month_day)
                )
                data = [db_bar.to_bar() for db_bar in s]
                return data
            s = (
                db_model.select(*fields)
                    .where(
                    (db_model.symbol == symbol)
                    & (db_model.interval == interval)
                    & (db_model.year_month_day >= start_y_m_d)
                    & (db_model.year_month_day <= end_y_m_d)
                )
                    .order_by(db_model.year_month_day)
            )
            data = [db_bar.to_bar() for db_bar in s]
            return data

    def get_bars_all(self,interval:Interval = None,with_detail=True)-> List[BarData]:
        with self.source.use_peewee() as db_model:
            if with_detail:
                fields = (
                db_model.symbol, db_model.year_month_day, db_model.interval, db_model.id_version, db_model.core_data,
                db_model.detail_data)
            else:
                fields = (
                db_model.symbol, db_model.year_month_day, db_model.interval, db_model.id_version, db_model.core_data)
            if interval is not None:
                s = (
                    db_model.select(*fields)
                    .where(db_model.interval == interval.value)
                    .order_by(db_model.year_month_day)
                )
            else:
                s = (
                    db_model.select(*fields)
                    .order_by(db_model.year_month_day)
                )
            data = [db_bar.to_bar() for db_bar in s]
            return data

    def get_bars_recently(self, symbol: str, time: datetime, count: int, interval: Interval = None, is_before=True,with_detail=True) -> List[BarData]:
        """
        返回time(包含当天）最近以前的bar行情数据:
        """
        _y_m_d, h_m_s = TimeUtils.split_datetime(time)
        if interval is None:
            interval = self.get_default_interval()
        interval = interval.value

        with self.source.use_peewee() as db_model:
            if with_detail:
                fields = (db_model.symbol,db_model.year_month_day,db_model.interval,db_model.id_version,db_model.core_data,db_model.detail_data)
            else:
                fields = (db_model.symbol,db_model.year_month_day,db_model.interval,db_model.id_version,db_model.core_data)
            if is_before:
                s = (
                    db_model.select(*fields)
                        .where(
                        (db_model.symbol == symbol)
                        & (db_model.interval == interval)
                        & (db_model.year_month_day <= _y_m_d)
                    )
                        .order_by(db_model.year_month_day.desc())
                        .limit(count)
                )
                data = [db_bar.to_bar() for db_bar in s]
                return list(reversed(data))
            else:
                s = (
                    db_model.select(*fields)
                        .where(
                        (db_model.symbol == symbol)
                        & (db_model.interval == interval)
                        & (db_model.year_month_day >= _y_m_d)
                    )
                        .order_by(db_model.year_month_day.asc())
                        .limit(count)
                )
                data = [db_bar.to_bar() for db_bar in s]
                return data

    def get_bars_at_day(self, time: datetime, symbols:List[str]=None, interval: Interval = None,with_detail = True) -> Dict[str, BarData]:
        _y_m_d, h_m_s = TimeUtils.split_datetime(time)
        if interval is None:
            interval = self.get_default_interval()
        interval = interval.value

        with self.source.use_peewee() as db_model:
            if with_detail:
                fields = (
                db_model.symbol, db_model.year_month_day, db_model.interval, db_model.id_version, db_model.core_data,
                db_model.detail_data)
            else:
                fields = (
                db_model.symbol, db_model.year_month_day, db_model.interval, db_model.id_version, db_model.core_data)
            condition = [db_model.interval == interval]
            condition.append(db_model.year_month_day == _y_m_d)
            if symbols:
                condition.append(db_model.symbol.in_(symbols))

            s = (
                db_model.select(*fields)
                    .where(*condition)
            )
            dict_data = {}
            for db_bar in s:
                bar = db_bar.to_bar()
                dict_data[bar.symbol] = bar
            return dict_data

    def get_newest_bar(self, symbol: str = None, interval=Interval.DAILY) -> Optional[BarData]:
        with self.source.use_peewee() as db_model:
            interval = interval.value
            s = (
                db_model.select()
                    .where(
                    (db_model.interval == interval)
                    & (db_model.symbol == symbol)
                )
                    .order_by(db_model.year_month_day.desc())
                    .first()
            )
            if s:
                return s.to_bar()
            return None



    def get_newest_datetime(self, symbol: str = None, interval=None) -> Optional[datetime]:

        with self.source.use_peewee() as db_model:
            if interval:
                interval = interval.value
                if symbol is None:
                    s = (
                        db_model.select(db_model.year_month_day)
                            .where(
                            (db_model.interval == interval)
                        )
                            .order_by(db_model.year_month_day.desc())
                            .first()
                    )
                    if s:
                        return to_datetime(s.year_month_day, the_day_end=True)
                else:
                    s = (
                        db_model.select(db_model.year_month_day)
                            .where(
                            (db_model.interval == interval)
                            & (db_model.symbol == symbol)
                        )
                            .order_by(db_model.year_month_day.desc())
                            .first()
                    )
                    if s:
                        return to_datetime(s.year_month_day, the_day_end=True)
            else:
                if symbol is None:
                    s = (
                        db_model.select(db_model.year_month_day)
                            .order_by(db_model.year_month_day.desc())
                            .first()
                    )
                    if s:
                        return to_datetime(s.year_month_day, the_day_end=True)
                else:
                    s = (
                        db_model.select(db_model.year_month_day)
                            .where(
                            (db_model.symbol == symbol)
                        )
                            .order_by(db_model.year_month_day.desc())
                            .first()
                    )
                    if s:
                        return to_datetime(s.year_month_day, the_day_end=True)
            return None

    def get_oldest_datetime(self, symbol: str = None, interval=Interval.DAILY) -> Optional[datetime]:
        with self.source.use_peewee() as db_model:
            if interval:
                interval = interval.value
                if symbol is None:
                    s = (
                        db_model.select(db_model.year_month_day)
                            .where(
                            (db_model.interval == interval)
                        )
                            .order_by(db_model.year_month_day.asc())
                            .first()
                    )
                    if s:
                        return to_datetime(s.year_month_day, the_day_end=True)
                else:
                    s = (
                        db_model.select(db_model.year_month_day)
                            .where(
                            (db_model.interval == interval)
                            & (db_model.symbol == symbol)
                        )
                            .order_by(db_model.year_month_day.asc())
                            .first()
                    )
                    if s:
                        return to_datetime(s.year_month_day, the_day_end=True)
            else:
                if symbol is None:
                    s = (
                        db_model.select(db_model.year_month_day)
                            .order_by(db_model.year_month_day.asc())
                            .first()
                    )
                    if s:
                        return to_datetime(s.year_month_day, the_day_end=True)
                else:
                    s = (
                        db_model.select(db_model.year_month_day)
                            .where(
                            (db_model.symbol == symbol)
                        )
                            .order_by(db_model.year_month_day.asc())
                            .first()
                    )
                    if s:
                        return to_datetime(s.year_month_day, the_day_end=True)
            return None


    def get_day_list(self, symbol: str = None,interval:Interval=None, start: datetime = None, end: datetime = None) -> List[datetime]:
        """
        param:
            symbol 允许为None
        """
        year_month_day_list = self._load_day_list_with_yy_mm_sss(symbol,interval, start, end)
        return [to_datetime(y_m_d, the_day_end=True) for y_m_d in year_month_day_list]

    def get_symbol_list(self) -> List[str]:
        with self.source.use_peewee() as db_model:
            s = (db_model.select(db_model.symbol).distinct())
            return [v.symbol for v in s]

    def _load_day_list_with_yy_mm_sss(self, symbol: str = None, interval:Interval = None, start: datetime = None,
                                     end: datetime = None) -> List:
        """"
        加载股票symbol的行情日
        """
        begin = 1
        finished = 99999999
        if not start is None:
            _y_m_d,_h_m_s = TimeUtils.split_datetime(start)
            begin = _y_m_d
        if not end is None:
            _y_m_d,_h_m_s = TimeUtils.split_datetime(end)
            finished = _y_m_d

        with self.source.use_peewee() as db_model:
            if interval is None:
                if symbol is None:
                    s = (db_model.select(db_model.year_month_day).where(
                            ##(db_model.interval == interval)
                            (db_model.year_month_day >= begin)
                            & (db_model.year_month_day <= finished)
                        ).order_by(db_model.year_month_day.asc()).distinct())
                else:
                    s = (
                        db_model.select(db_model.year_month_day).where(
                           # (db_model.interval == interval)
                            (db_model.symbol == symbol)
                            & (db_model.year_month_day > begin)
                            & (db_model.year_month_day < finished)
                        ).order_by(db_model.year_month_day.asc()).distinct()
                    )
            else:
                interval = interval.value
                if symbol is None:
                    s = (db_model.select(db_model.year_month_day).where(
                            (db_model.year_month_day >= begin)
                            & (db_model.interval == interval)
                            & (db_model.year_month_day <= finished)
                    ).order_by(db_model.year_month_day.asc()).distinct())
                else:
                    s = (
                        db_model.select(db_model.year_month_day).where(
                           # (db_model.interval == interval)
                            (db_model.symbol == symbol)
                            & (db_model.interval == interval)
                            & (db_model.year_month_day > begin)
                            & (db_model.year_month_day < finished)
                        ).order_by(db_model.year_month_day.asc()).distinct()
                    )
            year_month_day_list = [db_bar.year_month_day for db_bar in s]
        return year_month_day_list

    def size(self,symbol:str = None)->int:
        with self.source.use_peewee() as db_model:
            if symbol is None:
                return db_model.select().count()
            else:
                return db_model.select().where( db_model.symbol == symbol).count()

    def save_bars(self,bars:List[BarData]):
        """
        保存Bar数据。
        """
        ds = [BarDataModel.from_data(i) for i in bars]
        with self.source.use_peewee() as db_model:
            db_model.save_all(ds)


    def clear_at_interval(self, interval: Interval):
        interval =interval.value
        with self.source.use_peewee() as db_model:
            db_model.delete().where(
                (db_model.interval == interval)
            ).execute()

    def clear_before(self,time:datetime):
        with self.source.use_peewee() as db_model:
            y_m_d, h_m_s = TimeUtils.split_datetime(time)
            db_model.delete().where(
                (db_model.year_month_day <= y_m_d)
            ).execute()

    def clear(self, symbol: str = None):
        with self.source.use_peewee() as db_model:
            if not symbol is None :
                db_model.delete().where(
                    (db_model.symbol == symbol)
                ).execute()
            else:
                ##情况表
                db_model.drop_table()
                db_model.create_table()

    def get_day_db(self, day: datetime, interval = Interval.M1) -> DayDB:
        raise NotImplementedError("未实现")


